package com.qd.core.service.app;

import cn.hutool.core.util.PhoneUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.core.weixinmanager.domain.MiddleUserDTO;
import com.cdqidi.core.weixinmanager.event.WxMiddleUserUpdateEvent;
import com.cdqidi.core.weixinmanager.model.MiddleUser;
import com.cdqidi.core.weixinmanager.service.MiddleUserService;
import com.cdqidi.core.weixinmanager.service.OrgWxConfigService;
import com.cdqidi.core.weixinmanager.service.wx.WxaApiService;
import com.cdqidi.core.weixinmanager.util.CallWxUtil;
import com.cdqidi.dto.ApiResult;
import com.cdqidi.dto.ApiResultWrapper;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.ServletUtil;
import com.cdqidi.util.SpringContextHolder;
import com.jfinal.springboot.weixin.annotation.ApiType;
import com.qd.common.panda.config.PandaParkConfigDTO;
import com.qd.common.panda.constant.PandaConstant;
import com.qd.common.panda.domain.entity.hk.HkUserDTO;
import com.qd.common.panda.domain.entity.plate.UserPlateDTO;
import com.qd.common.panda.util.PlateColorUtil;
import com.qd.common.sys.constant.UserConstants;
import com.qd.common.sys.domain.entity.login.AppLoginUserDTO;
import com.qd.common.sys.domain.entity.login.LoginUserDTO;
import com.qd.common.sys.domain.login.AppUserExtend;
import com.qd.common.sys.domain.login.LoginBody;
import com.qd.common.sys.domain.login.LoginUser;
import com.qd.common.sys.domain.wx.WxUserDTO;
import com.qd.common.sys.enums.BaseTableEnum;
import com.qd.common.sys.enums.RemoteEnum;
import com.qd.common.sys.util.SecurityUtils;
import com.qd.core.security.token.BaseAuthenticationToken;
import com.qd.core.security.token.WxAuthenticationToken;
import com.qd.core.service.login.LoginHolderService;
import com.qd.hik.dto.HkBaseHttpResp;
import com.qd.hik.dto.driver.HkDriverInfoResp;
import com.qd.hik.dto.driver.HkPlateResp;
import com.qd.hik.service.HkDriverService;
import com.qd.panda.model.ChannelUser;
import com.qd.panda.model.UserPlateRelation;
import com.qd.panda.service.AsyncService;
import com.qd.panda.service.plate.PlateHolderService;
import com.qd.panda.service.plate.UserPlateRelationService;
import com.qd.panda.service.third.hk.HkUserService;
import com.qd.system.model.BaseUserInfoBind;
import com.qd.system.service.dict.MbUtil;
import com.qd.system.service.login.LoginUserService;
import com.qd.system.service.person.AuthTokenService;
import com.qd.system.service.user.BaseUserInfoBindService;
import com.qd.system.service.user.BaseUserInfoService;
import com.qd.system.util.FileConfirmUtil;
import com.qd.upload.constant.FileConfirmDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 小程序登录操作
 *
 * @author sjk
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class AppLoginService {


    private final AuthTokenService tokenService;
    private final AuthenticationManager authenticationManager;
    @Resource
    private WxaApiService wxaApiService;
    private final BaseUserInfoService baseUserInfoService;
    private final BaseUserInfoBindService baseUserInfoBindService;
    @Resource
    private MiddleUserService middleUserService;
    @Resource
    private OrgWxConfigService orgWxConfigService;
    @Resource
    private HttpServletRequest httpServletRequest;
    private final LoginUserService loginUserService;
    private final LoginHolderService loginHolderService;
    private final StringRedisTemplate stringRedisTemplate;
    private final HkUserService hkUserService;
    private final HkDriverService hkDriverService;
    private final PlateHolderService plateHolderService;
    private final PandaParkConfigDTO parkConfig;

    private final AuthTokenService authTokenService;
    private final UserPlateRelationService userPlateRelationService;
    private final AsyncService asyncService;

    private static void buildAppExtendByMiddleUser(AppUserExtend appUserExtend, MiddleUser middleUser) {
        if (!StringUtils.hasLength(appUserExtend.getAvatar()) && StringUtils.hasLength(middleUser.getHeadImg())) {
            if (middleUser.getHeadImg().contains(ConstantDto.HTTPS)) {
                appUserExtend.setAvatar(middleUser.getHeadImg());
            } else {
                appUserExtend.setAvatar(FileConfirmUtil.getImageFile(middleUser.getHeadImg()));
            }
        }
        if (!StringUtils.hasLength(appUserExtend.getOpenid()) && StringUtils.hasLength(middleUser.getOpenid())) {
            appUserExtend.setOpenid(middleUser.getOpenid());
        }
        if (!StringUtils.hasLength(appUserExtend.getMobile()) && StringUtils.hasLength(middleUser.getMobile())) {
            appUserExtend.setMobile(middleUser.getMobile());
        }
        if (!StringUtils.hasLength(appUserExtend.getNickName()) && StringUtils.hasLength(middleUser.getNickName())) {
            appUserExtend.setNickName(middleUser.getNickName());
        }
        if (!StringUtils.hasLength(appUserExtend.getAppId()) && StringUtils.hasLength(middleUser.getAppId())) {
            appUserExtend.setAppId(middleUser.getAppId());
        }
        if (StringUtils.hasLength(appUserExtend.getUnionId()) && StringUtils.hasLength(middleUser.getUnionId())) {
            appUserExtend.setUnionId(middleUser.getUnionId());
        }
    }

    @Transactional(readOnly = true)
    public String isLogin(String code, String appId, String promotionCode) {
        boolean exists = orgWxConfigService.isExistsById(appId);
        if (Boolean.FALSE.equals(exists)) {
            throw new ApiException("微信appId不存在");
        }
        if (log.isDebugEnabled()) {
            log.debug("code: {},appId:{}", code, appId);
        }
        final LoginUser loginUser = authTokenService.getLoginUser(httpServletRequest);
        if (log.isDebugEnabled()) {
            log.debug("loginUser:{}", loginUser);
        }
        String unionId = null;
        String openid = null;
        try {
            if (null == loginUser) {
                //token不存在，或者token失效
                final MiddleUser middleUser = buildMiddleUser(appId, code);
                if (null != middleUser) {
                    unionId = middleUser.getUnionId();
                    openid = middleUser.getOpenid();
                    return login(middleUser, promotionCode);
                }
            } else {
                unionId = loginUser.getUser().getAppUserExtend().getUnionId();
                openid = loginUser.getUser().getAppUserExtend().getOpenid();

                buildAppUserExtend(code, appId, loginUser);
                flushPromotionCode(promotionCode, loginUser);
                tokenService.refreshToken(loginUser);
                return tokenService.getToken(ServletUtil.getRequest());
            }
        } finally {
            if (StringUtils.hasLength(unionId) && StringUtils.hasLength(openid)) {
                //绑定微信公众号
                asyncService.createWxUser(openid, unionId);
            }
        }
        return "";

    }

    /**
     * 刷新推广码
     *
     * @param promotionCode 推广码
     * @param loginUser     登录信息
     */
    private void flushPromotionCode(String promotionCode, LoginUser loginUser) {
        Optional.ofNullable(loginUser).map(LoginUser::getUser).map(LoginUserDTO::getAppUserExtend).ifPresent(appUserExtend -> {
            if (StringUtils.hasLength(promotionCode)) {
                final String oldPromotionCode = appUserExtend.getPromotionCode();
                if (!StringUtils.hasLength(oldPromotionCode) || oldPromotionCode.equals(promotionCode)) {
                    if (MbUtil.isNotExists(promotionCode, BaseTableEnum.PROMOTION_CODE)) {
                        log.error("推广码错误: {}", promotionCode);
                        appUserExtend.setPromotionCode(null);
                    } else {
                        appUserExtend.setPromotionCode(promotionCode);
                    }
                }
            } else {
                appUserExtend.setPromotionCode(promotionCode);
            }
            if (log.isDebugEnabled()) {
                log.debug("推广码: {}", appUserExtend.getPromotionCode());
            }
            loginUser.getUser().setAppUserExtend(appUserExtend);
            authTokenService.refreshLoginUser(loginUser);
        });
    }

    private void buildAppUserExtend(String code, String appId, LoginUser loginUser) {
        AppUserExtend appUserExtend = Optional.of(loginUser.getUser()).map(LoginUserDTO::getAppUserExtend).orElse(null);
        if (null == appUserExtend || !StringUtils.hasLength(appUserExtend.getUnionId())) {
            final MiddleUser middleUser = buildMiddleUser(appId, code);
            if (null != middleUser) {
                if (null == appUserExtend) {
                    appUserExtend = new AppUserExtend();
                    loginUser.getUser().setAppUserExtend(appUserExtend);
                }
                buildAppExtendByMiddleUser(appUserExtend, middleUser);
            }
        }
    }

    private MiddleUser buildMiddleUser(String appId, String code) {
        final WxUserDTO wxUser = getWxUserDTO(appId, code);
        final MiddleUser middleUser = middleUserService.getMiddleUserByOpenidAndAppId(wxUser.getOpenid(), appId);
        if (null != middleUser && !StringUtils.hasLength(middleUser.getUnionId()) && StringUtils.hasLength(wxUser.getUnionId())) {
            middleUser.setUnionId(wxUser.getUnionId());
            SpringContextHolder.publishEvent(new WxMiddleUserUpdateEvent(middleUser));
        }
        return middleUser;
    }

    @Transactional(rollbackFor = Exception.class)
    public String register(AppLoginUserDTO dto) {
        if (!PhoneUtil.isPhone(dto.getMobile())) {
            throw new ApiException("请输入正确的手机号码");
        }
        boolean exists = orgWxConfigService.isExistsById(dto.getAppId());
        if (Boolean.FALSE.equals(exists)) {
            throw new ApiException("appId不存在");
        }
        final WxUserDTO wxUserDTO = getWxUserDTO(dto.getAppId(), dto.getCode());
        dto.setOpenid(wxUserDTO.getOpenid());
        if (StringUtils.hasLength(wxUserDTO.getUnionId())) {
            dto.setUnionId(wxUserDTO.getUnionId());
        }
        log.info("unionId: {}", wxUserDTO.getUnionId());
        final String uid = baseUserInfoService.getMobileToUid(dto.getMobile());
        LocalDateTime bindTime = LocalDateTime.now();
        buildBind(dto.getMobile(), uid, bindTime);
        MiddleUser middleUser = Optional.ofNullable(buildWxMiddleUser(dto, uid, bindTime)).orElseThrow(() -> new ApiException("用户关系信息为空"));
        syncHkPlateNumber(dto, middleUser);

        if(StringUtils.hasLength(dto.getNote())){
            ChannelUser channelUser = new ChannelUser();
            channelUser.setBindState(PandaConstant.CHANNEL_USER_BIND_STATE_0);
            channelUser.setChannelCode(dto.getNote());
            channelUser.setUserId(uid);
            channelUser.setMobile(dto.getMobile());
            channelUser.setBindTime(bindTime);
            asyncService.createChannelUser(channelUser);
        }


//        if (StringUtils.hasLength(dto.getNote())) {
//            MiddleUserExtend middleUserExtend = middleUserExtendService.getById(middleUser.getRId());
//            if (null == middleUserExtend) {
//                middleUserExtend = new MiddleUserExtend();
//                middleUserExtend.setRId(middleUser.getRId());
//                middleUserExtend.setNote(dto.getNote());
//                middleUserExtendService.save(middleUserExtend);
//                middleUserExtend.freeData();
//            }
//        }

        return login(middleUser, dto.getPromotionCode());
    }

    private void syncHkPlateNumber(AppLoginUserDTO dto, MiddleUser middleUser) {
        //到海康平台拉取车牌号
        HkBaseHttpResp<HkDriverInfoResp> driverInfoResp = hkDriverService.driverInfo(dto.getMobile());
        if (driverInfoResp.isSuccess()) {
            HkDriverInfoResp data = driverInfoResp.getData();
            if (null != data) {
                Integer driverId = data.getDriverId();
                if (null != driverId) {
                    HkBaseHttpResp<HkPlateResp> platesResp = hkDriverService.plates(driverId);
                    if (platesResp.isSuccess()) {
                        List<HkPlateResp.PlateInfo> results = platesResp.getData().getResults();
                        if (null != results && !results.isEmpty()) {
                            createHkUser(middleUser, driverId);
                            for (HkPlateResp.PlateInfo result : results) {
                                savePlate(middleUser, result);
                            }
                        }
                    } else {
                        log.error("到海康平台拉取车牌号异常: {}", platesResp.getMsg());
                    }
                }
            }
        } else {
            log.error("到海康平台拉取人员信息异常: {}", driverInfoResp.getMsg());
        }
    }

    private void savePlate(MiddleUser middleUser, HkPlateResp.PlateInfo result) {
        try {
            UserPlateRelation userPlateRelation = userPlateRelationService.getUniqueByPlateNumber(result.getPlateNo());
            if (null == userPlateRelation) {
                UserPlateDTO userPlateDto = new UserPlateDTO();
                userPlateDto.setPlateNumber(result.getPlateNo());
                userPlateDto.setUserId(middleUser.getUserId());
                userPlateDto.setVehicleColor(PlateColorUtil.hkToPanda(result.getPlateColor()));
                plateHolderService.save(userPlateDto, false);
                userPlateDto.freeData();
            }
        } catch (Exception e) {
            log.error("忽略错误: {}", e.getMessage());
        }
    }

    private void createHkUser(MiddleUser middleUser, Integer driverId) {
        HkUserDTO hkUserDTO = hkUserService.getByIdDto(middleUser.getUserId());
        if (null == hkUserDTO) {
            hkUserDTO = new HkUserDTO();
            hkUserDTO.setDriverId(driverId);
            hkUserDTO.setAddTime(LocalDateTime.now());
            hkUserDTO.setMobile(middleUser.getMobile());
            hkUserDTO.setUserId(middleUser.getUserId());
            hkUserService.save(hkUserDTO);
        }
        hkUserDTO.freeData();
    }

    public String login(MiddleUser middleUser) {
        return login(middleUser, null);
    }

    public String login(MiddleUser middleUser, String promotionCode) {
        try {
            // 该方法会去调用WxUserDetailServiceImpl.loadUserByUsername
            final Authentication authentication = authenticationManager.authenticate(new WxAuthenticationToken(middleUser.getRId(), middleUser.getUnionId()));
            final LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            tokenService.setUserAgent(loginUser);
            // 生成token
            if (loginUser.getUser().getAppUserExtend().getAppId().equals(parkConfig.getDefaultAppId())) {
                loginUser.setRemote(RemoteEnum.WX_APP.getCode());
            } else {
                loginUser.setRemote(RemoteEnum.MINI_PROGRAM.getCode());
            }
            final String token = tokenService.createToken(loginUser);
            flushPromotionCode(promotionCode, loginUser);
            return token;
        } catch (AuthenticationException e) {
            throw new ApiException(e.getMessage());
        }
    }

    /**
     * 用户名或者手机号作为账号，然后使用密码登陆
     *
     * @param mobile 手机号
     * @return token
     */
    @Transactional(readOnly = true)
    public String loginByAccount(String mobile) {
        final com.qd.system.model.LoginUser loginUser = Optional.ofNullable(loginUserService.getLoginUserByMobile(mobile)).orElseThrow(() -> new ApiException("账号不存在"));
        LoginBody loginBody = new LoginBody().setUsername(loginUser.getAccount()).setPassword("").setRemote(RemoteEnum.MINI_PROGRAM.getCode());
        return (String) loginHolderService.login(loginBody, new BaseAuthenticationToken(loginBody.getUsername(), loginBody.getPassword())).get("token");
    }

    public WxUserDTO getWxUserDTO(String appId, String code) {
        final WxUserDTO wxUser = CallWxUtil.callInterface(appId, ApiType.WXA, () -> {
            final ApiResultWrapper<Object> result = wxaApiService.getWxaOpenId(code);
            if (result.isSuccess()) {
                Object data = result.getData();
                if (data instanceof Map) {
                    Map<?, ?> map = (Map<?, ?>) result.getData();
                    final Object openid = map.get("openid");
                    final Object unionId = map.get("unionid");
                    WxUserDTO dto = new WxUserDTO();
                    dto.setAppId(appId);
                    if (null != unionId) {
                        dto.setUnionId(String.valueOf(unionId));
                    }
                    dto.setOpenid(String.valueOf(openid));
                    dto.setAppId(appId);
                    return dto;
                }

            }
            throw new ApiException("请求微信获得openId接口异常: " + result.getMsg());
        });
        return Optional.ofNullable(wxUser).orElseThrow(() -> new ApiException("获取用户信息为空"));
    }


    public MiddleUser buildWxMiddleUser(AppLoginUserDTO dto, String uid, LocalDateTime bindTime) {
        //是否和公众号或者小程序建立关系
        MiddleUser middleUser = middleUserService.getUniqueByUserIdAndAppId(uid, dto.getAppId());
        if (null == middleUser) {
            MiddleUserDTO middleUserDTO = new MiddleUserDTO();
            middleUserDTO.setRId(IdWorker.getIdStr())
                    .setBindTime(bindTime)
                    .setAppId(dto.getAppId())
                    .setUserId(uid)
                    .setMobile(dto.getMobile())
                    .setOpenid(dto.getOpenid())
                    .setUnionId(dto.getUnionId());
            if (StringUtils.hasLength(dto.getNickName())) {
                middleUserDTO.setNickName(dto.getNickName());
            }
            if (StringUtils.hasLength(dto.getAvatar())) {
                middleUserDTO.setHeadImg(dto.getAvatar());
            }
            middleUserService.save(middleUserDTO);
            return middleUserService.dtoToModel(middleUserDTO);
        } else {
            if (!middleUser.getOpenid().equals(dto.getOpenid())) {
                log.info("当前手机号码已经绑定: {}", dto.getOpenid());
                throw new ApiException("当前手机号已经绑定");
            }
        }
        return middleUser;
    }

    public void buildBind(String mobile, String uid, LocalDateTime bindTime) {
        final boolean b = Optional.ofNullable(baseUserInfoBindService.isExistsById(uid)).orElse(false);
        if (!b) {
            //是否绑定
            BaseUserInfoBind model = new BaseUserInfoBind();
            model.setMobile(mobile);
            model.setBindTime(bindTime);
            model.setUid(uid);
            baseUserInfoBindService.save(model);
            model.freeData();
        }
    }

    /**
     * 解除绑定
     *
     * @param appId  小程序ID
     * @param userId 用户ID
     * @return 成功 or 失败
     */
    @Transactional(rollbackFor = Exception.class)
    public ApiResultWrapper<Object> unBind(String appId, String userId) {
        if (!StringUtils.hasLength(userId)) {
            final LoginUser loginUser = SecurityUtils.getLoginUser(false);
            if (null != loginUser) {
                userId = loginUser.getUserId();
            }
        }
        if (!StringUtils.hasLength(userId)) {
            return ApiResult.error("用户ID不能为空");
        }
        final List<MiddleUser> list = middleUserService.getList(new MiddleUserDTO().setUserId(userId));
        final Iterator<MiddleUser> iterator = list.iterator();
        while (iterator.hasNext()) {
            final MiddleUser middleUser = iterator.next();
            if (middleUser.getUserId().equals(userId) && middleUser.getAppId().equals(appId)) {
                middleUserService.removeById(middleUser.getRId());
                iterator.remove();
            }
        }
        if (list.isEmpty()) {
            baseUserInfoBindService.removeById(userId);
            final String tokenId = tokenService.getUserIdKey(userId, RemoteEnum.MINI_PROGRAM.getCode());
            final String token = stringRedisTemplate.opsForValue().get(tokenId);
            log.info("tokenId: {},token: {}", tokenId, token);
            if (StringUtils.hasLength(token)) {
                stringRedisTemplate.delete(tokenId);
                stringRedisTemplate.delete(token);
            }
        }
        asyncService.unbindChannelUser(userId);
        return ApiResult.success("解绑成功");
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateLoginUser(String userId, String nickName, String headImg) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        MiddleUser middleUser = middleUserService.getUniqueByUserIdAndAppId(userId, parkConfig.getDefaultMiniProgramAppId());
        if (null == middleUser) {
            throw new ApiException("不存在绑定信息无法修改");
        }
        MiddleUserDTO middleUserDTO = new MiddleUserDTO();
        middleUserDTO.setRId(middleUser.getRId());
        if (StringUtils.hasLength(nickName)) {
            middleUserDTO.setNickName(nickName);
            loginUser.getUser().getAppUserExtend().setNickName(nickName);
        }
        if (StringUtils.hasLength(headImg)) {
            middleUserDTO.setHeadImg(getFileConfirm(headImg).getOfficialFile());
            loginUser.getUser().getAppUserExtend().setAvatar(middleUserDTO.getHeadImg());
        }
        middleUserService.update(middleUserDTO);
        tokenService.refreshToken(loginUser);
    }

    private FileConfirmDTO getFileConfirm(String avatar) {
        final FileConfirmDTO fileConfirm = FileConfirmUtil.singleFileConfirm(avatar, UserConstants.IMAGE);
        if (null == fileConfirm) {
            throw new ApiException("上传头像确认失败");
        }
        return fileConfirm;
    }

}
